#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//与重定向有关的全局变量
#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

int redir = NONE_REDIR;
char* filename = NULL; 

//全局变量
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;

#define MAXENVS 128
char* g_env[MAXENVS];
int g_envs = 0;

//for test 
char cwd[1024];
char addenv[1024];

//退出信息
int lastCode = 0;

//command
const char* getHomePath();

bool myCd()
{
    std::string cmd =  g_argv[0];
    if(g_argc == 1)
    {
        std::string where = getHomePath();
        if(where == "NONE") return false;
        chdir(where.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        if(where == "-")
        {
            //...
        }
        else if(where == "~")
        {
            //...
        }
        else 
        {
            chdir(where.c_str());
        }
    }
    return true;
}

bool myEcho()
{   
        if(g_argc == 2)
        {            
            std::string option = g_argv[1];
            if(option[0] == '$')    
            {
                if(option[1] == '?')
                {
                    std::cout << lastCode << std::endl;
                    lastCode = 0;
                    return true;
                }
                else 
                {
                    std::string envName = option.substr(1);
                    std::string envValue = getenv(envName.c_str());
                    std::cout << envValue << std::endl;
                    lastCode = 0;
                    return true;
                }
             }
        }
        lastCode = 1;
        return false;
}
const char* getUserName()
{
    const char* userName = getenv("USER");
    return userName == NULL ? "NONE" : userName;
}

const char* getHostName()
{
    const char* hostName = getenv("HOSTNAME");
    return hostName == NULL ? "NONE" : hostName;
}

const char* getHomePath()
{
    const char* homePath = getenv("HOME");
    return homePath == NULL ? "NONE" : homePath;
}

const char* getPwd()
{
    char* pwd = getcwd(cwd, sizeof(cwd));
    snprintf(addenv, sizeof(addenv), "PWD=%s\n", cwd);
    putenv(addenv);
    return pwd == NULL ? "NONE" : pwd;
}

const char* getDir(const char* pwd)
{
#define SLASH "/"
    std::string dir(pwd);
    if(dir == SLASH) return SLASH;
    size_t pos = dir.rfind(SLASH, std::string::npos);
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos + 1).c_str();
}

void makeCommandPrompt(char cmdPrompt[], int size)
{
   snprintf(cmdPrompt, size, FORMAT, getUserName(), getHostName(), getDir(getPwd())); 
}

void printCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    makeCommandPrompt(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdin);
}

bool getCommandLine(char commandLine[], int size)
{
    char* c = fgets(commandLine, size, stdin);
    if(c == NULL) return false;
    commandLine[strlen(commandLine) - 1] = 0;
    if(strlen(commandLine) == 0) return false;
    return true;
}

void trimSpace(char*& filename)
{
    while(isspace(*filename))
    {
        filename++;
    }
}

void checkRedir(char cmd[], int len)
{
    redir = NONE_REDIR;
    filename = NULL;
    int start = 0, end = len - 1;
    while(end >= start)
    {
        if(cmd[end] == '>')
        {
            if(end > 0 && cmd[end - 1] == '>')
            {
                redir = APPEND_REDIR;
                cmd[end] = 0;
                cmd[end - 1] = 0;
                filename = &cmd[end] + 1;
                trimSpace(filename);
                break;
            }
            else 
            {
                redir = OUTPUT_REDIR;
                cmd[end] = 0;
                filename = &cmd[end] + 1;
                trimSpace(filename);
                break;
            }
        }
        else if(cmd[end] == '<')
        {
            redir = INPUT_REDIR;
            cmd[end] = 0;
            filename = &cmd[end] + 1;
            trimSpace(filename);
            break;
        }
        else end--;
    }
}

bool commandParse(char commandLine[])
{
    checkRedir(commandLine, strlen(commandLine));
    //printf("redir->%d\n", redir);
    //printf("filename->%s\n" ,filename);
    g_argc = 0;
    char* content = strtok(commandLine, " ");
    while(content != NULL)
    {
        g_argv[g_argc++] = content;
        content = strtok(NULL, " ");
    }
    g_argv[g_argc] = NULL;
    if(redir != NONE_REDIR) return true;
    return g_argc == 0 ? false : true;
}

void doRedir()
{
    if(filename == NULL)
        return;
    if(redir == INPUT_REDIR)
    {
        int fd = open(filename, O_RDONLY);
        if(fd < 0)
        {
            perror("failed to open for reading");
            exit(1);
        }
        dup2(fd, 0);
    }
    else if(redir == OUTPUT_REDIR)
    {
        int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
        if(fd < 0)
        {
            perror("failed to open for writing");
            exit(2);
        }
        dup2(fd, 1);
    }
    else if(redir == APPEND_REDIR)
    {
        int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
        if(fd < 0)
        {
            perror("failed to open for appending");
            exit(3);
        }
        dup2(fd, 1);
    }
    else;
}

bool commandExecute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        doRedir();        
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    //father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastCode = WEXITSTATUS(status);
        }
        else lastCode = 100;
    return true;
    }
    return false;
}

bool checkAndExecuteBuiltin()
{
    if(g_argv[0] == NULL) return false;
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        myCd();
        return true;
    }
    else if(cmd == "export")
    {
        //myExport();
        return true;
    }
    else if(cmd == "echo")
    {
        myEcho();
        return true;
    }
    return false;
}

void debugArgv()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("g_argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("%d\n", g_argc);
}

void initEnv()
{
    memset(g_env, 0, sizeof(g_env));
    extern char** environ;
    int i = 0;
    g_envs = 0;
    for(; environ[i]; i++)
    {
        g_env[g_envs] = (char*)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[g_envs++], environ[i]);
    }
    g_env[g_envs++] = (char*)"HAHA=1234567";
    int j = 0;
    for(; g_env[j]; j++)
    {
        putenv(g_env[j]);
    }
    environ = g_env;
}

int main()
{
    //从父 shell 中获取环境变量
    initEnv();
    while(1)
    {
        //1. 输出命令行提示符
        printCommandPrompt();

        char commandLine[COMMAND_SIZE];
        if(!getCommandLine(commandLine, sizeof(commandLine)))
            continue;

        //3. 命令行分析
        if(!commandParse(commandLine))
        continue;
        //debugArgv();

        //4. 检测并处理内建命令
        if(checkAndExecuteBuiltin())
            continue;

        //5. 执行命令
        commandExecute();
    }

    return 0;
}
